﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Collections.ObjectModel;

using SharedGenomics.Core;
using SharedGenomics.Domain;

namespace SharedGenomics.DataAccess
{
    public class BCSnpMaxGenotypeFileParser : IGenotypeDataFileParser
    {
        private ValidationMessageCollection _validationMessages;
        private IEnumerable<string> _lines;
        private IList<string> _markerNames;
        private IList<string> _individualNames;

        public BCSnpMaxGenotypeFileParser(string filePath)
            : this(new TextFileReader(filePath))
        {
        }

        public BCSnpMaxGenotypeFileParser(IEnumerable<string> lines)
            : this(lines, new MarkerGenotypeResolver())
        {
        }

        public BCSnpMaxGenotypeFileParser(IEnumerable<string> lines, MarkerGenotypeResolver genotypeResolver)
        {
            genotypeResolver.ThrowIfNull("genotypeResolver");

            this.Lines = lines;
            this.GenotypeResolver = genotypeResolver;
            this._validationMessages = new ValidationMessageCollection();

            this.Initialise();
        }

        public IEnumerator<IndividualGenotype> GetEnumerator()
        {
            IndividualGenotype currentIndividual = null;
            string currentId = null;

            GenotypeDataRow genotypeRow;
            foreach (var line in this.Lines ?? Enumerable.Empty<string>())
            {
                if (this.TryGetGenotypeRow(line, 0, false, out genotypeRow))
                {
                    string id = genotypeRow.IndividualId;

                    if (id != currentId)
                    {
                        currentId = id;
                        if (currentIndividual != null)
                            yield return currentIndividual;

                        Sex s = this.SexLookup == null ? Sex.Unknown : this.SexLookup[id];
                        currentIndividual = new IndividualGenotype(genotypeRow.IndividualId, s);
                    }

                    MarkerGenotype genotype = this.GenotypeResolver.ResolveGenotype(genotypeRow.Marker, genotypeRow.Allele1, genotypeRow.Allele2);
                    currentIndividual.AddMarkerGenotype(genotypeRow.Marker, genotype);
                }
            }

            if (currentIndividual != null)
            {
                yield return currentIndividual;
            }
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        private void Initialise()
        {
            int lineNumber = 0;
            HashSet<string> individualNames = new HashSet<string>();
            HashSet<string> markerNames = new HashSet<string>();

            GenotypeDataRow currentDataRow;

            //skip header line
            foreach (string line in this.Lines.Skip(1))
            {
                lineNumber++;

                if (this.TryGetGenotypeRow(line, lineNumber, true, out currentDataRow))
                {
                    individualNames.Add(currentDataRow.IndividualId);
                    markerNames.Add(currentDataRow.Marker);
                }
            }

            this._individualNames = individualNames.ToList();
            this._markerNames = markerNames.ToList();
        }

        private bool TryGetGenotypeRow(string line, int lineNumber, bool logValidationErrors, out GenotypeDataRow row)
        {
            Debug.Assert(line != null);
            row = null;

            //ignore whitespace-only lines
            if (line.IsEmptyOrWhitespace())
                return false;

            string[] lineElements = line.Split('\t');

            //check line has enough elements
            if (lineElements.Length < 4)
            {
                if(logValidationErrors)
                    this._validationMessages.AddWarningMessage(String.Format("Line {0} invalid: 4 elements required - recieved {1}", lineNumber, lineElements.Length));

                return false;
            }

            Debug.Assert(lineElements.Length >= 4);

            string id = lineElements[0];
            string markerName = lineElements[1];
            string allele1 = lineElements[2];
            string allele2 = lineElements[3];

            //add warning and skip if either individual id or marker name is missing
            if (id.IsEmptyOrWhitespace())
            {
                if(logValidationErrors)
                    this._validationMessages.AddWarningMessage(String.Format("Line {0} invalid: Empty individual name", lineNumber));

                return false;
            }

            if (markerName.IsEmptyOrWhitespace())
            {
                if(logValidationErrors)
                    this._validationMessages.AddWarningMessage(String.Format("Line {0} invalid: Empty marker name", lineNumber));
                return false;
            }

            row = new GenotypeDataRow(id, markerName, allele1, allele2);
            return true;
        }

        public IndividualSexLookup SexLookup { get; set; }
        private IEnumerable<string> Lines
        {
            get { return this._lines; }
            set { this._lines = value ?? Enumerable.Empty<string>(); }
        }
        private MarkerGenotypeResolver GenotypeResolver { get; set; }

        #region IGenotypeDataFileParser Members

        public ReadOnlyCollection<string> IndividualNames
        {
            get { return new ReadOnlyCollection<string>(this._individualNames); }
        }

        public ReadOnlyCollection<string> MarkerNames
        {
            get { return new ReadOnlyCollection<string>(this._markerNames); }
        }

        public IValidationMessageCollection ValidationMessages
        {
            get { return this._validationMessages; }
        }

        #endregion
    }
}
